#include "ringbuffer.h"
#include "assert.h"

/**
 * initialize the specified ringbuffer
 * @param prbuf: the descriptor of ringbuffer
 * @param buf: data buffer for ringbuffer
 * @param size: the size of data buffer
 * @return: void
 */
void mx_ringbuffer_init (mx_ringbuffer_pt prbuf, mx_uint8_pt buf, mx_usize_t size)
{
    assert_true(prbuf != mx_null);
    assert_true(buf != mx_null);
    assert_true(size != 0);

    prbuf->buffer = buf;
    prbuf->rpointer = 0;
    prbuf->wpointer = 0;
    prbuf->size = size;
    prbuf->hit = mx_false;
}

/**
 * put a char data to ringbuffer
 * @param prbuf: the descriptor of ringbuffer
 * @param data: char data
 * @return: void
 */
void mx_ringbuffer_putchar (mx_ringbuffer_pt prbuf, mx_uint8_t data)
{
    assert_true(prbuf != mx_null);
    
    if (prbuf->wpointer != prbuf->rpointer)
    {
        prbuf->buffer[prbuf->wpointer] = data;
        if (prbuf->wpointer >= (prbuf->size - 1))
        {
            prbuf->wpointer = 0;
        }
        else
        {
            prbuf->wpointer ++;
        }
        if (prbuf->wpointer == prbuf->rpointer)
        {
            prbuf->hit = mx_true;
        }
    }
    else
    {
        if (!prbuf->hit)
        {
            prbuf->buffer[prbuf->wpointer] = data;
            if (prbuf->wpointer >= (prbuf->size - 1))
            {
                prbuf->wpointer = 0;
            }
            else
            {
                prbuf->wpointer ++;
            }
            if (prbuf->wpointer == prbuf->rpointer)
            {
                prbuf->hit = mx_true;
            }
        }
    }
}

/**
 * get a char data from the ringbuffer
 * @param prbuf: the descriptor of ringbuffer
 * @return: the char data, return MX_RINGBUFFER_NULL means that no data be got
 */
mx_int16_t mx_ringbuffer_getchar (mx_ringbuffer_pt prbuf)
{
    assert_true(prbuf != mx_null);
    
    mx_int16_t ret = -1;
    if (prbuf->wpointer != prbuf->rpointer)
    {
        ret = prbuf->buffer[prbuf->rpointer];
        if (prbuf->rpointer >= (prbuf->size - 1))
        {
            prbuf->rpointer = 0;
        }
        else
        {
            prbuf->rpointer ++;
        }
    }
    else
    {
        if (prbuf->hit)
        {
            ret = prbuf->buffer[prbuf->rpointer];
            if (prbuf->rpointer >= (prbuf->size - 1))
            {
                prbuf->rpointer = 0;
            }
            else
            {
                prbuf->rpointer ++;
            }
            prbuf->hit = mx_false;
        }
    }
    return ret;
}

/**
 * calculate the effective data length of the ringbuffer
 * @param prbuf: the descriptor of ringbuffer
 * @void: effective data lenght
 */
mx_usize_t mx_ringbuffer_count (mx_ringbuffer_pt prbuf)
{
    assert_true(prbuf != mx_null);
    
    mx_usize_t count = 0;
    if (prbuf->wpointer == prbuf->rpointer)
    {
        if (prbuf->hit)
        {
            count = prbuf->size;
        }
    }
    else
    {
        if (prbuf->wpointer > prbuf->rpointer)
        {
            count = prbuf->wpointer - prbuf->rpointer;
        }
        else
        {
            count = prbuf->size - (prbuf->rpointer - prbuf->wpointer);
        }
    }
    return count;
}

/**
 * get all the effective data from ringbuffer
 * @param prbuf: the descriptor of ringbuffer
 * @param arr: point to a buffer that store the data from ringbuffer
 *             buffer size better larger than the ringbuffer size avoid buffer overflow
 * @return: the length of received data
 */
mx_usize_t mx_ringbuffer_getall (mx_ringbuffer_pt prbuf, mx_uint8_pt arr)
{
    assert_true(prbuf != mx_null);
    assert_true(arr != mx_null);
    
    mx_usize_t count = 0;
    mx_int16_t chr;
    do {
        chr = mx_ringbuffer_getchar(prbuf);
    } while (chr != MX_RINGBUFFER_NULL &&
            ({arr[count] = (mx_uint8_t)chr; count ++; mx_true;}));
    return count;
}

/**
 * clear(reset) the ringbuffer
 * @param prbuf: the descriptor of ringbuffer
 * @return: void
 */
void mx_ringbuffer_clear (mx_ringbuffer_pt prbuf)
{
    assert_true(prbuf != mx_null);
    
    prbuf->hit = 0;
    prbuf->rpointer = 0;
    prbuf->wpointer = 0;
}

/**
 * delete last puted data
 * @param prbuf: the descriptor of ringbuffer
 * @param backnum: delete number
 * @return: void
 */
void mx_ringbuffer_backspace (mx_ringbuffer_pt prbuf, mx_usize_t backnum)
{
    assert_true(prbuf != mx_null);
    
    mx_usize_t backpoint, validsize;
    validsize = mx_ringbuffer_count(prbuf);
    if (backnum >= validsize)
    {
        mx_ringbuffer_clear(prbuf);
    }
    else
    {
        if (prbuf->wpointer > prbuf->rpointer)
        {
            prbuf->wpointer -= backnum;
        }
        else
        {
            if (prbuf->wpointer >= backnum)
            {
                prbuf->wpointer -= backnum;
            }
            else
            {
                prbuf->wpointer = prbuf->size - (backnum - prbuf->wpointer);
            }
        }
    }
}
